﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nes4Net
{
    public class Rom
    {
        public Nes Nes { get; private set; }
        public String[] MapperName { get; private set; }
        public Rom(Nes nes)
        {
            this.Nes = nes;
            this.MapperName = new String[92];
            Array.Fill(this.MapperName, "Unknown Mapper");
            this.MapperName[0] = "Direct Access";
            this.MapperName[1] = "Nintendo MMC1";
            this.MapperName[2] = "UNROM";
            this.MapperName[3] = "CNROM";
            this.MapperName[4] = "Nintendo MMC3";
            this.MapperName[5] = "Nintendo MMC5";
            this.MapperName[6] = "FFE F4xxx";
            this.MapperName[7] = "AOROM";
            this.MapperName[8] = "FFE F3xxx";
            this.MapperName[9] = "Nintendo MMC2";
            this.MapperName[10] = "Nintendo MMC4";
            this.MapperName[11] = "Color Dreams Chip";
            this.MapperName[12] = "FFE F6xxx";
            this.MapperName[15] = "100-in-1 switch";
            this.MapperName[16] = "Bandai chip";
            this.MapperName[17] = "FFE F8xxx";
            this.MapperName[18] = "Jaleco SS8806 chip";
            this.MapperName[19] = "Namcot 106 chip";
            this.MapperName[20] = "Famicom Disk System";
            this.MapperName[21] = "Konami VRC4a";
            this.MapperName[22] = "Konami VRC2a";
            this.MapperName[23] = "Konami VRC2a";
            this.MapperName[24] = "Konami VRC6";
            this.MapperName[25] = "Konami VRC4b";
            this.MapperName[32] = "Irem G-101 chip";
            this.MapperName[33] = "Taito TC0190/TC0350";
            this.MapperName[34] = "32kB ROM switch";

            this.MapperName[64] = "Tengen RAMBO-1 chip";
            this.MapperName[65] = "Irem H-3001 chip";
            this.MapperName[66] = "GNROM switch";
            this.MapperName[67] = "SunSoft3 chip";
            this.MapperName[68] = "SunSoft4 chip";
            this.MapperName[69] = "SunSoft5 FME-7 chip";
            this.MapperName[71] = "Camerica chip";
            this.MapperName[78] = "Irem 74HC161/32-based";
            this.MapperName[91] = "Pirate HK-SF3 chip";

        }

        #region CONST

        public const Int32 VERTICAL_MIRRORING = 0;
        public const Int32 HORIZONTAL_MIRRORING = 1;
        public const Int32 FOURSCREEN_MIRRORING = 2;
        public const Int32 SINGLESCREEN_MIRRORING = 3;
        public const Int32 SINGLESCREEN_MIRRORING2 = 4;
        public const Int32 SINGLESCREEN_MIRRORING3 = 5;
        public const Int32 SINGLESCREEN_MIRRORING4 = 6;
        public const Int32 CHRROM_MIRRORING = 7;
        #endregion
        #region field
        private Int32[] header = null;
        public Int32[][] rom = null;
        public Int32[][] vrom = null;
        public Tile[][] vromTile = null;

        public Int32 romCount;
        public Int32 vromCount;
        private Int32 mirroring;
        public Boolean batteryRam;
        private Boolean trainer;
        private Boolean fourScreen;
        private Int32 mapperType;
        public Boolean valid = false;

        #endregion
        
        public void Load(Byte[] data)
        {
            Int32 i, j, v;
            if (data[0] != 78
                || data[1] != 69
                || data[2] != 83)
            {
                throw new Exception("Not a valid NES ROM.");
            }
            this.header = new Int32[16];
            for (i = 0; i < 16; i++)
            {
                this.header[i] = Convert.ToInt32(data[i]) & 0xff;
            }

            this.romCount = this.header[4];
            this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB
            this.mirroring = (this.header[6] & 1) != 0 ? 1 : 0;
            this.batteryRam = (this.header[6] & 2) != 0;
            this.trainer = (this.header[6] & 4) != 0;
            this.fourScreen = (this.header[6] & 8) != 0;
            this.mapperType = (this.header[6] >> 4) | (this.header[7] & 0xf0);

            /* TODO
            if (this.batteryRam)
                this.loadBatteryRam();*/
            // Check whether byte 8-15 are zero's:
            var foundError = false;
            for (i = 8; i < 16; i++)
            {
                if (this.header[i] != 0)
                {
                    foundError = true;
                    break;
                }
            }
            if (foundError)
            {
                this.mapperType &= 0xf; // Ignore byte 7
            }
            // Load PRG-ROM banks:
            this.rom = new Int32[this.romCount][];
            var offset = 16;
            for (i = 0; i < this.romCount; i++)
            {
                this.rom[i] = new Int32[16384];
                for (j = 0; j < 16384; j++)
                {
                    if (offset + j >= data.Length)
                    {
                        break;
                    }
                    this.rom[i][j] = Convert.ToInt32(data[offset + j]) & 0xff;
                }
                offset += 16384;
            }
            // Load CHR-ROM banks:
            this.vrom = new Int32[this.vromCount][];
            for (i = 0; i < this.vromCount; i++)
            {
                this.vrom[i] = new Int32[4096];
                for (j = 0; j < 4096; j++)
                {
                    if (offset + j >= data.Length)
                    {
                        break;
                    }
                    this.vrom[i][j] = Convert.ToInt32(data[offset + j]) & 0xff;
                }
                offset += 4096;
            }

            // Create VROM tiles:
            this.vromTile = new Tile[this.vromCount][];
            for (i = 0; i < this.vromCount; i++)
            {
                this.vromTile[i] = new Tile[256];
                for (j = 0; j < 256; j++)
                {
                    this.vromTile[i][j] = new Tile();
                }
            }

            // Convert CHR-ROM banks to tiles:
            Int32 tileIndex;
            Int32 leftOver;
            for (v = 0; v < this.vromCount; v++)
            {
                for (i = 0; i < 4096; i++)
                {
                    tileIndex = i >> 4;
                    leftOver = i % 16;
                    if (leftOver < 8)
                    {
                        this.vromTile[v][tileIndex].SetScanline(
                          leftOver,
                          this.vrom[v][i],
                          this.vrom[v][i + 8]
                        );
                    }
                    else
                    {
                        this.vromTile[v][tileIndex].SetScanline(
                          leftOver - 8,
                          this.vrom[v][i - 8],
                          this.vrom[v][i]
                        );
                    }
                }
            }

            this.valid = true;
        }

        public Int32 GetMirroringType ()
        {
            if (this.fourScreen)
            {
                return FOURSCREEN_MIRRORING;
            }
            if (this.mirroring == 0)
            {
                return HORIZONTAL_MIRRORING;
            }
            return VERTICAL_MIRRORING;
        }

        public String getMapperName ()
        {
            if (this.mapperType >= 0 && this.mapperType < this.MapperName.Length)
            {
                return this.MapperName[this.mapperType];
            }
            return "Unknown Mapper, " + this.mapperType;
        }

        public Mapper0 createMapper ()
        {
            var result = Mappers.GetMapper(this.mapperType, this.Nes);
            if (result is null)
            {
                throw new Exception($"This ROM uses a mapper not supported by JSNES: ({ this.getMapperName() + this.mapperType})");
            }
            else
            {
                return result;
            }
        }
    }
}
